package task1

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	"log"
	"math/big"

	"github.com/day01/config"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	cfg := config.MustLoadConfig("config/config.yaml")
	if cfg == nil {
		panic("加载配置错误")
	}

	client := connectNetWork(cfg)


   findBlock(client)
   
   sendTransaction()



}



func connectNetWork(config *config.Config)(*ethclient.Client){
	client, err := ethclient.Dial(config.RPC.SepoliaWS)
	if err != nil {
		panic("链接网络错误")
	}
	return  client

}


func findBlock(client *ethclient.Client)  {

	  //// 创建一个大整数对象，指定要查询的区块号为5671744，获取指定的区块头信息
    blockNumber := big.NewInt(5671744)

	// 根据区块号获取区块头信息（区块头包含区块的元数据，但不包含交易详情）
	header, err := client.HeaderByNumber(context.Background(), blockNumber)
	if err != nil {
		log.Fatalf("获取区块头信息错误: %v", err)
	}
	// 输出区块号
	fmt.Println(header.Number.Uint64())
	//打印区块的时间戳(unix时间戳,表示区块生成的时间)
	fmt.Println(header.Time)
	//打印区块的父区块哈希值
	fmt.Println(header.ParentHash.Hex())
	//打印区块的交易根哈希值
	fmt.Println(header.TxHash.Hex())
	//打印区块的 uncle 根哈希值
	fmt.Println(header.UncleHash.Hex())

		// 根据区块号获取完整的区块信息（包含所有交易详情）
	block, err := client.BlockByNumber(context.Background(), blockNumber)
	// 检查获取区块是否出错
	if err != nil {
		// 如果获取失败，记录错误并终止程序
		log.Fatal(err)
	}
	// 打印完整区块中的区块号（转换为uint64类型）
	fmt.Println(block.Number().Uint64()) // 5671744
	// 打印完整区块的时间戳（Unix时间戳）
	fmt.Println(block.Time()) // 1712798400
	// 打印完整区块的难度值
	fmt.Println(block.Difficulty().Uint64()) // 0
	// 打印完整区块的哈希值（十六进制格式）
	fmt.Println(block.Hash().Hex()) // 0xae713dea1419ac72b928ebe6ba9915cd4fc1ef125a606f90f5e783c47cb1a4b5
	// 打印区块中包含的交易数量（通过获取交易列表的长度）
	fmt.Println(len(block.Transactions())) // 70 交易列表
	// 根据区块哈希获取该区块中的交易总数
	count, err := client.TransactionCount(context.Background(), block.Hash())
	// 检查获取交易数量是否出错
	if err != nil {
		// 如果获取失败，记录错误并终止程序
		log.Fatal(err)
	}

	// 打印区块中的交易总数
	fmt.Println(count) // 70



		// 遍历区块中的所有交易，tx为当前交易对象，_表示忽略索引
	for _, tx := range block.Transactions() {
		// 打印交易的哈希值（十六进制格式）
		fmt.Println(tx.Hash().Hex()) // 0x20294a03e8766e9aeab58327fc4112756017c6c28f6f99c7722f4a29075601c5
		// 打印交易的转账金额（以wei为单位的字符串）
		fmt.Println(tx.Value().String()) // 100000000000000000
		// 打印交易的Gas限额（最多可使用的Gas数量）
		fmt.Println(tx.Gas()) // 21000
		// 打印交易的Gas价格（每个Gas单位的价格，以wei为单位）
		fmt.Println(tx.GasPrice().Uint64()) // 100000000000
		// 打印交易的nonce值（发送者账户的交易计数器）
		fmt.Println(tx.Nonce()) // 245132
		// 打印交易的附加数据（通常为空或包含智能合约调用数据）
		fmt.Println(tx.Data()) // []
		// 打印交易的接收方地址（十六进制格式）
		fmt.Println(tx.To().Hex()) // 交易的接收方  0x8F9aFd209339088Ced7Bc0f57Fe08566ADda3587

		// 使用EIP155签名算法获取交易的发送者地址，如果成功则err为nil
		if sender, err := types.Sender(types.NewEIP155Signer(chainID), tx); err == nil {
			// 打印交易发送者的地址（十六进制格式）
			fmt.Println("sender", sender.Hex()) // 0x2CdA41645F2dBffB852a605E92B185501801FC28
		} else { // 如果获取发送者失败
			// 记录错误并终止程序
			log.Fatal(err)
		}

		// 根据交易哈希获取交易收据，包含交易执行结果
		receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
		// 检查获取收据是否出错
		if err != nil {
			// 如果获取失败，记录错误并终止程序
			log.Fatal(err)
		}

		// 打印交易收据状态（1表示成功，0表示失败）
		fmt.Println(receipt.Status) // 1  查询收据状态
		// 打印交易产生的日志事件（空数组表示没有事件）
		fmt.Println(receipt.Logs) // []  查询日志
		// 跳出循环，只查看第一笔交易
		break // 只看第一笔交易
	}

}



func sendTransaction(config *config.Config,client *ethclient.Client)  { 
	//// 从配置文件读取私钥并加载
	privateKey, err := crypto.HexToECDSA(config.Accounts.Main.PrivateKey)
	if err != nil {
		log.Fatal(err)
	}
		// 从私钥中提取公钥（interface{}类型）
	publicKey := privateKey.Public()

	// 将公钥断言为*ecdsa.PublicKey类型
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		// 如果类型断言失败，记录错误并终止程序
		log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
	}

	// 从公钥生成以太坊地址（发送方地址）
	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	// 从链上查询该地址的nonce值（账户已发送的交易数量），通过PendingNonceAt获取待处理的nonce
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		// 如果查询nonce失败，记录错误并终止程序
		log.Fatal(err)
	}

	// 从配置文件读取转账金额
	value := new(big.Int)
	value.SetString(config.Transaction.EthAmount, 10) // 从配置文件读取（单位：wei）
	// 设置gas限制为21000单位（标准ETH转账的gas消耗）
	gasLimit := uint64(21000) // in units
	// 从网络获取建议的gas价格（动态gas价格）
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		// 如果获取gas价格失败，记录错误并终止程序
		log.Fatal(err)
	}


	// 从配置文件读取接收方地址
	toAddress := common.HexToAddress(config.Addresses.Recipient)
	// 创建空的data字段（普通ETH转账不需要data）
	var data []byte
	// 创建一个新的交易对象，包含nonce、接收方地址、金额、gas限制、gas价格和data
	tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)

	// 获取当前网络的Chain ID（Sepolia测试网的Chain ID是11155111）
	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		// 如果获取Chain ID失败，记录错误并终止程序
		log.Fatal(err)
	}

	// 使用EIP155签名方法对交易进行签名（防止重放攻击）
	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		// 如果签名失败，记录错误并终止程序
		log.Fatal(err)
	}

	// 将已签名的交易发送到以太坊网络
	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		// 如果发送交易失败，记录错误并终止程序（常见错误：余额不足）
		log.Fatal(err)
	}

	// 打印交易哈希（可用于在区块链浏览器中查询交易状态）
	fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex())

}

/*

把 64/66 位十六进制私钥字符串 还原成  *ecdsa.PrivateKey  对象，供后面签名、交易、地址派生使用



import "github.com/ethereum/go-ethereum/crypto"

func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error)




二、它到底做了什么（源码速览）
1. 
长度检查
允许 64 字符（裸 32 字节）
允许 66 字符（带  0x  前缀）
2. 
去掉  0x  前缀，解码成 32 字节  []byte 
3. 
调用  crypto.ToECDSA(bytes) 
把 32 字节解释成 大整数 D（secp256k1 私钥）
计算公钥  P = D * G （椭圆曲线 secp256k1 基点乘法）
组装成  *ecdsa.PrivateKey  并返回




 crypto.HexToECDSA  就是以太坊世界里的 “私钥字符串 → 可用私钥对象” 的转换器，拿到  *ecdsa.PrivateKey  
 后你就可以签名交易、派生地址、搞 HD 钱包等后续操作。

*/



/*
1. 
根节点需求
 Context 在 Go 里被设计为一棵 不可变、逆向传播取消信号 的树。
 任何子 Context 都必须知道“我爸是谁”，才能把自己的  Done()  通道挂到父亲下面。
 于是必须有一个 没有爸爸 的节点作为整棵树的起点，这就是  Background 。

2. 
接口约束
  context.Context  是一个接口，只要实现 4 个方法即可：
go
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any

Background 的 4 个方法全部返回“零值”或  nil ，表示：
永不超时
永不取消
没有错误
不携带键值对

二、源码级拆解：它到底长什么样
src/context/context.go 里只有 20 行左右


go

type emptyCtx int

func (emptyCtx) Deadline() (deadline time.Time, ok bool) { return }
func (emptyCtx) Done() <-chan struct{}                   { return nil }
func (emptyCtx) Err() error                              { return nil }
func (emptyCtx) Value(key any) any                       { return nil }
func (emptyCtx) String() string                          { return "context.Background" }

var background = new(emptyCtx)

func Background() Context { return background }

 emptyCtx  用  int  做底层类型，零成本。
全局变量  background  在包初始化时只创建一次，以后永远返回同一个地址，无内存分配。
因为它啥也不做，所以并发安全，可以随便传。

三、使用套路：从“根”到“叶子”的生命周期

go

func main() {
    root := context.Background()
    ...
}


2. 
中间层 —— 派生需求
 需求 | 派生函数 | 典型代码  ---|---|---  
 超时 |  WithTimeout  | ctx, cancel := context.WithTimeout(root, 3*time.Second)   
  
 定时 |  WithDeadline  |  ctx, cancel := context.WithDeadline(root, time.Now().Add(3*time.Second)) 
 手动取消 |  WithCancel  |  ctx, cancel := context.WithCancel(root)   
 传值 |  WithValue  |  ctx := context.WithValue(root, key, "user-123") 



 3. 
下游函数 —— 只读即可

go

func queryDB(ctx context.Context, id int) (Row, error) {
    // 1. 监听取消信号
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    default:
    }

    // 2. 继续把 ctx 传给驱动，让驱动也能感知取消
    return driver.QueryContext(ctx, "SELECT … WHERE id=?", id)
}



四、常见误区与踩坑

1. 
直接透传 Background


http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // 错误：这里应该拿 r.Context()，而不是 Background
    ctx := context.Background()
    callSlowRPC(ctx)   // 客户端断开了，RPC 还在傻跑
})


正确姿势：HTTP 请求本身已经带了一个可取消的 Context，用  r.Context()  即可




在 goroutine 里“死等”

go func() {
    // 错误：用 Background 就永远等，进程退出不了
    <-context.Background().Done()
}()



3. 
WithValue 当 Map 用

 Context 的 Value 只存 请求作用域的全局元数据（trace-id、user-id），不要把它当普通 map 塞业务数据。
 原因：key 是接口，查找是线性回溯，性能差；且不可变，每次 WithValue 都要新建节点。



五、一张图总结（文字版）

 main()
   │
   ├─ Background() ──→ 根 Context（永不取消）
        │
        ├─ WithTimeout(3s) ──→ 子 Context（3 秒后自动取消）
        │     │
        │     └─ http.Get(ctx, url)  // 超时后 http 包会中断请求
        │
        └─ WithValue(traceID, "123") ──→ 子 Context
              │
              └─ log.Println(ctx.Value(traceID)) // 全链路日志打同一个 ID



六、一句话收束

context.Background()  就是 Context 世界里的“宇宙大爆炸奇点”：
它自己啥也不干，但没有它，整个取消链、超时链、传值链都无从谈起；
在程序最顶层拿它当“根”，然后一路  WithXxx  派生下去，千万别把根直接塞进会阻塞的调用里

 */